import base64
import os
import tempfile
import uuid
from datetime import datetime
from io import BytesIO
from langchain.memory import  ConversationBufferWindowMemory
from flask import  session
import librosa
from injector import inject
from dataclasses import dataclass
from langchain_core.language_models import BaseChatModel
from langchain_core.memory import BaseMemory
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from openai import OpenAI

from werkzeug.datastructures import FileStorage
from src.entity.upload_entity import ALLOW_IMG_EXTENSION
from langchain_core.runnables import RunnablePassthrough, RunnableConfig, RunnableLambda
import speech_recognition as sr

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider
import dotenv
import whisper
client = OpenAI(api_key="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")

from src.exception.exception import FailException

dotenv.load_dotenv()


@inject
@dataclass
class ChatService:
    memory: BaseMemory
    llm: BaseChatModel

    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4o-2024-08-06", temperature=0)
        self.parse = StrOutputParser()


        self.recognizer = sr.Recognizer()
        self.SYSTEMPL = """
        You are an expert who focuses on answering mathematical problems and can answer corresponding questions through images uploaded by users or text input.
        When answering science questions, corresponding problem-solving steps can be generated, with each step number separated by a colon. The steps are as follows:
        1: Analyze the type and difficulty of the question.
        2: Provide detailed problem-solving ideas and steps.
        3: Show the calculation formula and process.
        The following are examples of problem-solving steps:
        Solution equation: 3x+5=14
        Solution steps:
        Analyze the type and difficulty of the question:
        This is a one-dimensional linear equation solving problem.
        The difficulty is relatively low, mainly focusing on the solution of linear equations, including term shifting and division operations.
        Provide detailed problem-solving ideas and steps:
        Step 1: Identify the type of equation. This is a linear equation with the form ax+b=c.
        Step 2: Move the unknown and constant terms in the equation to both sides of the equation, so that the unknown terms are on one side of the equal sign and the constant terms are on the other side. This is usually achieved through item shifting operations.
        Step 3: Simplify the equation and solve for the unknown variables through division operations.
        Show the calculation formula and process:
        Step 1: Identify the type of equation
        The equation 3x+5=14 is a linear equation.
        Step 2: Transfer items
        Moving the constant term 5 from equation 3x+5=14 to the other side of the equal sign yields:
        3x = 14 - 5
        3x = 9
        Step 3: Solve for unknown variables
        Divide both sides of the equation by 3 simultaneously to obtain:
        x = 9 / 3
        x = 3
        Answer around the above content, do not answer questions unrelated to science, do not reply to content unrelated to problem-solving steps, only reply to problem-solving steps, and generate in Japanese
        User's question: {query}"""
        self.prompt_sim = """
        You are an expert who can generate relevant questions based on the provided content
        Please provide 3 relevant questions based on the following context, do not answer irrelevant ones
        """


    def upload(self,file:FileStorage):
        auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
        bucket = oss2.Bucket(auth, os.getenv("ENDPOINT"), os.getenv("BUCKET_NAME"), region=os.getenv("REGION"))
        file_name = file.filename
        extension = file_name.rsplit(".", 1)[-1] if "." in file_name else ""
        if extension.lower() not in ALLOW_IMG_EXTENSION:
            raise FailException(f"该扩展名{extension}不允许上传，请上传正确的图片格式")
        f = file.stream.read()
        new_file_name = f"{uuid.uuid4()}.{extension}"
        now = datetime.now()
        upload_file_name = f"{now.year}/{now.month:02d}/{now.day:02d}/{new_file_name}"
        bucket.put_object(upload_file_name, f)

        return f"{os.getenv('BUCKET_URL')}/{upload_file_name}"

    def upload_image(self, url: str,query:str):

        promps = ChatPromptTemplate.from_messages([
            ("human", [
                {"type": "text", "text": query},
                {"type": "image_url",
                 "image_url": {
                     "url": "{img_url}"
                 }
                 }
            ])
        ])
        chain = ({
                     "img_url": RunnablePassthrough()
                 } | promps | self.llm | self.parse)
        text_val = chain.invoke(url)
        return text_val

    def sim(self, query):
        p_sim = ChatPromptTemplate.from_messages([
            ("system", self.prompt_sim),
            ("human", "{query}")
        ])
        chain_sim = ({
                         "query": RunnablePassthrough(),
                     } | p_sim | self.llm | self.parse)
        return chain_sim.invoke(query)


    def audio_text(self, file: FileStorage):
        with tempfile.TemporaryDirectory() as temp_dir:
            file_path = os.path.join(temp_dir, str(uuid.uuid4()) + os.path.splitext(file.filename)[1])
            with open(file_path, "wb") as f:
                f.write(file.read())
                with open(file_path, "rb") as audio_file:
                    transcription = client.audio.transcriptions.create(
                        model="whisper-1",
                        file=audio_file
                    )
                    return self.chat(transcription.text)

    def chat(self, query):
        print(session["username"])
        p = ChatPromptTemplate.from_template(self.SYSTEMPL)
        chain = (
                {
                    "query": RunnablePassthrough(),

                } |  p | self.llm | self.parse
        )
        return chain.invoke(query)
